package com.feeyo.prophet.actions.flight;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.feeyo.llm.agent.engine.*;
import com.feeyo.llm.util.JsonUtil;
import com.feeyo.prophet.plugins.FlightScheduleActionUtils;
import com.feeyo.prophet.pojo.action.flight.FlightPriceReq;
import com.feeyo.prophet.pojo.action.flight.FlightPirceResp;
import com.feeyo.prophet.pojo.action.functioncall.flight.FlightPriceProperties;
import com.feeyo.prophet.pojo.action.functioncall.FunctionCall;
import com.feeyo.prophet.pojo.action.functioncall.ItemsBean;
import com.feeyo.prophet.pojo.action.functioncall.flight.common.BasicTypeDefaultList;
import com.feeyo.prophet.config.ParametersSingletonManager;
import com.feeyo.prophet.plugins.AnswerUtils;
import com.feeyo.prophet.plugins.DbSearchActionUtils;
import com.feeyo.prophet.plugins.FleetAttributesActionUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 运价
 */

@Slf4j
public class FlightPriceAction implements Action {

    private final String host;
    private final Integer port;
    private final String priceHost;
    private final Integer pricePort;

    public FlightPriceAction(String host, Integer port, String priceHost, Integer pricePort) {
        this.host = host;
        this.port = port;
        this.priceHost = priceHost;
        this.pricePort = pricePort;
    }

    @Override
    public String human() {
        return "运价查询";
    }

    @Override
    public String name() {
        return "FlightPriceAction";
    }

    @Override
    public String eg() {
        return """
                Question: 北京首都国际机场飞上海虹桥机场6月20号运价
                Thought: 我应该使用FlightPriceAction工具来查找运价信息
                Action: FlightPriceAction
                Action Input: {"depCodes":"BJS","arrCodes":"TYO","flightDate":"2024-06-20"}
                """;
    }

    @Override
    public String description() {
        return """
                用于运价查询,给出指定航班日期和出发机场、到达机场,查询运价信息
                """;
    }

    @Override
    public String parameters() {
        String parameters = ParametersSingletonManager.getParameters("FlightPriceAction", () -> {
            FunctionCall functionCall = new FunctionCall();
            functionCall.setType("object");
            functionCall.setRequired(Lists.newArrayList("departureDate", "depCodes", "arrCodes", "fields"));
            FlightPriceProperties properties = new FlightPriceProperties();
            properties.setTicketDate(new BasicTypeDefaultList("string", "出票日期，格式: yyyy-MM-dd，例如: '2024-07-25'"));
            properties.setDepartureDate(new BasicTypeDefaultList("string", "出发日期，格式: yyyy-MM-dd，例如: '2024-07-26'"));
            properties.setDepCityCodes(new BasicTypeDefaultList("string", "出发城市IATA三字码，例如: 'BJS'"));
            properties.setArrCityCodes(new BasicTypeDefaultList("string", "到达城市IATA三字码，例如: 'TYO'"));
            properties.setFnums(new BasicTypeDefaultList("array", "航班号集合，例如: ['HU7248']", new ItemsBean("string")));
            List<ResultScheme.ResultAttach> resultAttachList = result().getResultAttachList();
            List<String> fieldsList = resultAttachList.stream().map(ResultScheme.ResultAttach::getName).toList();
            // 修改 fields 部分，确保生成正确的字段列表
            properties.setFields(new BasicTypeDefaultList("array", "返回结果字段集合(根据需要多选), 各个字段含义解释如下: " + JsonUtil.marshalToString(resultAttachList)
                    + "。可选填字段如下: " + String.join(", ", fieldsList) + ",你需要尽量全面的选出适用字段。可以选多，不要选少。", new ItemsBean("string"), 1));
            functionCall.setProperties(properties);
            return functionCall.toString();
        });
        return parameters;
    }

    @Override
    public ParameterScheme defaultParameters() {
        Map<String, ParameterScheme.Property> properties = new HashMap<>();
        properties.put("ticketDate", new ParameterScheme.Property("string", "出票日期，格式: yyyy-MM-dd，例如: '2024-07-25'", null, null, null, null, null));
        properties.put("departureDate", new ParameterScheme.Property("string", "出发日期，格式: yyyy-MM-dd，例如: '2024-07-26'", null, null, null, null, null));
        properties.put("depCodes", new ParameterScheme.Property("string", "出发城市IATA三字码，例如: 'BJS'"));
        properties.put("arrCodes", new ParameterScheme.Property("string", "到达城市IATA三字码，例如: 'TYO'"));
        properties.put("fnums", new ParameterScheme.Property("array", "航班号集合，例如: ['HU7248']", new ParameterScheme.Property.Items("string", null, null, null), null, null, null, null));
        List<ResultScheme.ResultAttach> resultAttachList = result().getResultAttachList();
        List<String> fieldsList = resultAttachList.stream().map(ResultScheme.ResultAttach::getName).toList();
        properties.put("fields", new ParameterScheme.Property("array", "返回结果字段集合(根据需要多选), 字段含义: " + JsonUtil.marshalToString(resultAttachList)
                + "，可选字段: " + String.join(", ", fieldsList), new ParameterScheme.Property.Items("string", null, null, null), null, null, 1, null));
        ParameterScheme parameterScheme = new ParameterScheme("object", List.of("startDate", "depCodes", "arrCodes", "fields"), properties);
        return parameterScheme;
    }

    @Override
    public ResultScheme result() {
        ResultScheme resultScheme = new ResultScheme();
        List<ResultScheme.ResultAttach> resultAttachList = Lists.newArrayList();
        resultAttachList.add(new ResultScheme.ResultAttach("price", "string", "票价"));
        resultAttachList.add(new ResultScheme.ResultAttach("flightNo", "string", "航班"));
        resultAttachList.add(new ResultScheme.ResultAttach("fdeptDate", "string", "航班日期"));
        resultAttachList.add(new ResultScheme.ResultAttach("fdepTime", "string", "出发时间"));
        resultAttachList.add(new ResultScheme.ResultAttach("farrTime", "string", "到达时间"));
        resultAttachList.add(new ResultScheme.ResultAttach("deptCode", "string", "出发机场"));
        resultAttachList.add(new ResultScheme.ResultAttach("arrCode", "string", "到达机场"));
        resultAttachList.add(new ResultScheme.ResultAttach("airline", "string", "航司"));
        resultAttachList.add(new ResultScheme.ResultAttach("shareFlightNo", "string", "共享航班号"));
        resultAttachList.add(new ResultScheme.ResultAttach("cabinCode", "string", "仓位代码"));
        resultAttachList.add(new ResultScheme.ResultAttach("cabinClass", "string", "舱位等级"));
        resultAttachList.add(new ResultScheme.ResultAttach("seatsNum", "float", "仓位余票"));
        resultScheme.setResultAttachList(resultAttachList);
        return resultScheme;
    }

    @Override
    public ActionResult run(String question, String input, ExecuteOptions options) throws Exception {
        try {
            FlightPriceReq req = JSON.parseObject(input, FlightPriceReq.class);
            // 出票日期
            String ticketDate = req.getTicketDate();
            // 出发日期
            String departureDate = req.getDepartureDate();
            List<String> fields = req.getFields();
            if(StringUtils.isEmpty(departureDate)){
                return new ActionResult(HttpStatus.BAD_REQUEST.value(), "航班日期必填。请使用 conversation action 让用户补充这些参数");
            }
            if(StringUtils.isEmpty(req.getDepCityCodes()) || StringUtils.isEmpty(req.getArrCityCodes())){
                return new ActionResult(HttpStatus.BAD_REQUEST.value(), "航班出发地、到达地必填。请使用 conversation action 让用户补充这些参数");
            }
            if(StringUtils.isEmpty(ticketDate)){
                ticketDate = departureDate;
            }
            LocalDate departureDateLocal = LocalDate.parse(departureDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            LocalDate localNow = LocalDate.now();
            // 日期判断
            LocalDate threeDaysAgoLocal = localNow.minusDays(3);
            LocalDate thirtyPlusDays = localNow.plusDays(30);
            if (departureDateLocal.isBefore(threeDaysAgoLocal)) {
                return new ActionResult(HttpStatus.BAD_REQUEST.value(), "只允许查询三天前和三十天内的数据,请使用 conversation action 让用户补充这些参数");
            }
            if(departureDateLocal.isAfter(thirtyPlusDays)){
                return new ActionResult(HttpStatus.BAD_REQUEST.value(), "只允许查询三天前和三十天内的数据,请使用 conversation action 让用户补充这些参数");
            }
            List<String> fnumList = req.getFnums();
            List<String> depCodeList = Lists.newArrayList();
            List<String> arrCodeList = Lists.newArrayList();
            if(StringUtils.isNotEmpty(req.getDepCityCodes())){
                depCodeList = FleetAttributesActionUtils.getAirportIata(host, port, req.getDepCityCodes());
            }
            if(StringUtils.isNotEmpty(req.getArrCityCodes())){
                arrCodeList = FleetAttributesActionUtils.getAirportIata(host, port, req.getArrCityCodes());
            }
            if (CollectionUtils.isEmpty(depCodeList) || CollectionUtils.isEmpty(arrCodeList)) {
                return new ActionResult(HttpStatus.BAD_REQUEST.value(), "出发地,到达地必须存在,请使用 conversation action 让用户补充这些参数");
            }
            Set<FlightPirceResp> flightStatisticsResp;
            if(departureDateLocal.isBefore(localNow)){
                flightStatisticsResp = searchHistoryPriceByDate(departureDate, departureDate, ticketDate, depCodeList, arrCodeList, fnumList);
            }else{
                flightStatisticsResp = searchPriceByDate(departureDate, departureDate, depCodeList, arrCodeList, fnumList);
            }
            ResultScheme resultScheme = FlightScheduleActionUtils.resultScheme(fields, result());
            return AnswerUtils.answer(name(), input, flightStatisticsResp, resultScheme);
        } catch (Throwable e) {
            log.error("error", e);
            return new ActionResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), "解析发生错误，请思考后再调用？");
        }
    }

    /**
     * 数据查询
     */
    private Set<FlightPirceResp> searchPriceByDate(String startDate, String endDate, List<String> forgList, List<String> fdstList, List<String> fnumList) {
        Set<FlightPirceResp> flightPriceRespList = Sets.newHashSet();
        for (String forg : forgList) {
            for (String fdst : fdstList) {
                List<String> respList = Lists.newArrayList();
                String searchResp;
                if (CollectionUtils.isNotEmpty(fnumList)) {
                    for (String fnum : fnumList) {
                        searchResp = DbSearchActionUtils.searchFeaturePriceByDate(priceHost, pricePort, startDate, endDate, forg, fdst, fnum);
                        respList.add(searchResp);
                    }
                } else {
                    searchResp = DbSearchActionUtils.searchFeaturePriceByDate(priceHost, pricePort, startDate, endDate, forg, fdst, null);
                    respList.add(searchResp);
                }
                if (CollectionUtils.isNotEmpty(respList)) {
                    List<FlightPirceResp> flightPirceRespList = Lists.newArrayList();
                    for (String resp : respList) {
                        JSONObject json = JSONObject.parseObject(resp);
                        int code = json.getIntValue("code");
                        if (code == HttpStatus.OK.value()) {
                            JSONArray data = json.getJSONArray("data");
                            if (!data.isEmpty()) {
                                for (Object rowData : data) {
                                    FlightPirceResp financialReportResp = JSON.parseObject(String.valueOf(rowData), FlightPirceResp.class);
                                    Instant instant = Instant.ofEpochSecond(financialReportResp.getDepDate());
                                    String formattedDate = instant.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                                    financialReportResp.setFdeptDate(formattedDate);
                                    financialReportResp.setFdepTime(Instant.ofEpochSecond(financialReportResp.getDepTime()).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
                                    financialReportResp.setFarrTime(Instant.ofEpochSecond(financialReportResp.getArrTime()).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
                                    flightPirceRespList.add(financialReportResp);
                                }
                            }
                        }
                    }
                    // 按照 flightNo 分组
                    Map<String, List<FlightPirceResp>> groupedByFlightNo = flightPirceRespList.stream().collect(Collectors.groupingBy(FlightPirceResp::getFlightNo));
                    for (Map.Entry<String, List<FlightPirceResp>> entry : groupedByFlightNo.entrySet()) {
                        List<FlightPirceResp> groupedList = entry.getValue();
                        Map<String, List<FlightPirceResp>> groupedByFdeptDate = groupedList.stream().collect(Collectors.groupingBy(FlightPirceResp::getFdeptDate));
                        for (Map.Entry<String, List<FlightPirceResp>> fdeptDateEntry : groupedByFdeptDate.entrySet()) {
                            List<FlightPirceResp> fdeptValueList = fdeptDateEntry.getValue();
                            fdeptValueList.sort(Comparator.comparing(value -> Double.parseDouble(value.getPrice())));
                            // 获取第一条记录
                            FlightPirceResp firstRecord = fdeptValueList.getFirst();
                            flightPriceRespList.add(firstRecord);
                            // 获取第一条 seatsNum > 0 的记录
                            FlightPirceResp firstSeatsNumGtZeroRecord = fdeptValueList.stream().filter(record -> record.getSeatsNum() > 0).findFirst().orElse(null);
                            flightPriceRespList.add(firstSeatsNumGtZeroRecord);
                            // 获取最后一条记录
                            FlightPirceResp lastRecord = fdeptValueList.getLast();
                            flightPriceRespList.add(lastRecord);
                        }
                    }
                }
            }
        }
        return flightPriceRespList;
    }

    private Set<FlightPirceResp> searchHistoryPriceByDate(String startDate, String endDate, String fdeptDate, List<String> forgList, List<String> fdstList, List<String> fnumList) {
        Set<FlightPirceResp> flightPriceRespList = Sets.newHashSet();
        for (String forg : forgList) {
            for (String fdst : fdstList) {
                List<String> respList = Lists.newArrayList();
                String searchResp;
                if (CollectionUtils.isNotEmpty(fnumList)) {
                    for (String fnum : fnumList) {
                        searchResp = DbSearchActionUtils.searchHistoryPriceByDate(priceHost, pricePort, startDate, endDate, forg, fdst, fnum, fdeptDate);
                        respList.add(searchResp);
                    }
                } else {
                    searchResp = DbSearchActionUtils.searchHistoryPriceByDate(priceHost, pricePort, startDate, endDate, forg, fdst, null, fdeptDate);
                    respList.add(searchResp);
                }
                if (CollectionUtils.isNotEmpty(respList)) {
                    List<FlightPirceResp> flightPirceRespList = Lists.newArrayList();
                    for (String resp : respList) {
                        JSONObject json = JSONObject.parseObject(resp);
                        int code = json.getIntValue("code");
                        if (code == HttpStatus.OK.value()) {
                            JSONArray data = json.getJSONArray("data");
                            if (!data.isEmpty()) {
                                for (Object rowData : data) {
                                    FlightPirceResp financialReportResp = JSON.parseObject(String.valueOf(rowData), FlightPirceResp.class);
                                    Instant instant = Instant.ofEpochSecond(financialReportResp.getDepDate());
                                    String formattedDate = instant.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                                    financialReportResp.setFdeptDate(formattedDate);
                                    financialReportResp.setFdepTime(Instant.ofEpochSecond(financialReportResp.getDepTime()).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
                                    financialReportResp.setFarrTime(Instant.ofEpochSecond(financialReportResp.getArrTime()).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
                                    flightPirceRespList.add(financialReportResp);
                                }
                            }
                        }
                    }
                    // 按照 flightNo 分组
                    Map<String, List<FlightPirceResp>> groupedByFlightNo = flightPirceRespList.stream().collect(Collectors.groupingBy(FlightPirceResp::getFlightNo));
                    for (Map.Entry<String, List<FlightPirceResp>> entry : groupedByFlightNo.entrySet()) {
                        List<FlightPirceResp> groupedList = entry.getValue();
                        Map<String, List<FlightPirceResp>> groupedByFdeptDate = groupedList.stream().collect(Collectors.groupingBy(FlightPirceResp::getFdeptDate));
                        for (Map.Entry<String, List<FlightPirceResp>> fdeptDateEntry : groupedByFdeptDate.entrySet()) {
                            List<FlightPirceResp> fdeptValueList = fdeptDateEntry.getValue();
                            fdeptValueList.sort(Comparator.comparing(value -> Double.parseDouble(value.getPrice())));
                            // 获取第一条记录
                            FlightPirceResp firstRecord = fdeptValueList.getFirst();
                            flightPriceRespList.add(firstRecord);
                            // 获取第一条 seatsNum > 0 的记录
                            FlightPirceResp firstSeatsNumGtZeroRecord = fdeptValueList.stream().filter(record -> record.getSeatsNum() > 0).findFirst().orElse(null);
                            flightPriceRespList.add(firstSeatsNumGtZeroRecord);
                            // 获取最后一条记录
                            FlightPirceResp lastRecord = fdeptValueList.getLast();
                            flightPriceRespList.add(lastRecord);
                        }
                    }
                }
            }
        }
        return flightPriceRespList;
    }
}
